package com.crypto.oauth;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Arrays;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
import com.google.api.client.auth.oauth2.BearerToken;
import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.http.json.JsonHttpParser;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.gson.Gson;

public class CryptoOAuthClient implements Serializable {

	private static final long serialVersionUID = 1L;

	/** OAuth 2 scope. */
	private static final String SCOPE = "read";

	/** Global instance of the HTTP transport. */
	private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();

	/** Global instance of the JSON factory. */
	static final JsonFactory JSON_FACTORY = new JacksonFactory();

	private static final String SERVER_BASE_URL = "http://localhost:8080/CryptoOAuthServer";
	
	private static final String TOKEN_SERVER_URL         = SERVER_BASE_URL + "/oauth/token";
	private static final String AUTHORIZATION_SERVER_URL = SERVER_BASE_URL + "/oauth/authorize";
	private static final String CLIENT_ID                = "clientId";
	private static final String CLIENT_SECRET            = "clientSecret";
	private static final String CALLBACK_URL             = "http://localhost:9090/";

	
	/*------------------------------------ Public methods ----------------------------------*/
	
	/**
	 * Confidential clients or other clients issued client credentials MUST authenticate with 
	 * the authorization server as described in Section 2.3 when making requests to the token endpoint.
	 * 
	 * @return the access token response for the client login
	 * @throws ClientProtocolException if the end point URL does not specify a valid host name
	 * @throws IOException in case of an IO problem or if the connection was aborted
	 */
	public ClientLoginResponse loginClient() throws ClientProtocolException, IOException {
		
		Gson gson = new Gson();
		DefaultHttpClient httpClient = new DefaultHttpClient();
		
		BufferedReader br = this.executeRequest(httpClient, TOKEN_SERVER_URL+"?grant_type=client_credentials&client_id="+CLIENT_ID+"&client_secret="+CLIENT_SECRET);
		ClientLoginResponse clientLoginResponse = gson.fromJson(br, ClientLoginResponse.class);

		httpClient.getConnectionManager().shutdown();
		return clientLoginResponse;
	}
	
	/**
	 * Gets the authorization code request url used to redirect the resource owner to the server 
	 * to obtain your compliance.
	 * 
	 * @return the authorization code request url
	 */
	public String getAuthorizationCodeRequestUrl() {
		return new AuthorizationCodeRequestUrl(AUTHORIZATION_SERVER_URL, CLIENT_ID).setRedirectUri(CALLBACK_URL).setScopes(Arrays.asList(SCOPE)).build();
	}
	
	/**
	 * Request the access token and then obtain the user contacts from se resource server.
	 * 
	 * @param code the code for obtain the access token
	 * @return the contact as a String
	 * @throws IOException in case of an IO problem or if the connection was aborted
	 */
	public String getAccessToken(String code) throws IOException {
		
		final Credential credential = authorize(code, CALLBACK_URL);
		
		HttpRequestFactory requestFactory = HTTP_TRANSPORT
				.createRequestFactory(new HttpRequestInitializer() {
					@Override
					public void initialize(HttpRequest request)
							throws IOException {
						credential.initialize(request);
						request.addParser(new JsonHttpParser(JSON_FACTORY));
					}
				});

		CryptoOAuthServerUrl url = new CryptoOAuthServerUrl(SERVER_BASE_URL + "/contact/jsonListForUser/2");

		HttpRequest request = requestFactory.buildGetRequest(url);
		return request.execute().parseAsString();
	}

	/*--------------------------------------------------------------------------------------*/
	/*----------------------------------- Private methods ----------------------------------*/

	private Credential authorize(String code, String redirectUri) throws IOException {
		
		AuthorizationCodeFlow codeFlow = new AuthorizationCodeFlow.Builder(
				BearerToken.authorizationHeaderAccessMethod(), 
				HTTP_TRANSPORT,
				JSON_FACTORY, 
				new GenericUrl(TOKEN_SERVER_URL),
				new ClientParametersAuthentication(CLIENT_ID, CLIENT_SECRET), 
				CLIENT_ID, 
				AUTHORIZATION_SERVER_URL
		).setScopes(Arrays.asList(SCOPE)).build();

		TokenResponse response = codeFlow.newTokenRequest(code).setRedirectUri(redirectUri).setScopes(Arrays.asList(SCOPE)).execute();
		return codeFlow.createAndStoreCredential(response, null);
	}
	
	private BufferedReader executeRequest(DefaultHttpClient httpClient, String url) throws ClientProtocolException, IOException {
		
		HttpGet getRequest = new HttpGet(url);
		HttpResponse response = httpClient.execute(getRequest);

		if (response.getStatusLine().getStatusCode() != 200) {
			throw new RuntimeException("Failed : HTTP error code : " + response.getStatusLine().getStatusCode());
		}

		return new BufferedReader(new InputStreamReader((response.getEntity().getContent())));
	}

	/*--------------------------------------------------------------------------------------*/
	
}
